{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-zMKQx6DkKwt"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "J307vsiDkMMW"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vCMYwDIE9dTT"
      },
      "source": [
        "# Keras functional API в TensorFlow"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lAJfkZ-K9flj"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/functional\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />Смотрите на TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ru/guide/keras/functional.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Запустите в Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ru/guide/keras/functional.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />Изучайте код на GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ru/guide/keras/functional.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Скачайте ноутбук</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fj66ZXAzrJC2"
      },
      "source": [
        "Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует [официальной документации на английском языке](https://www.tensorflow.org/?hl=en). Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в [tensorflow/docs](https://github.com/tensorflow/docs) репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на [docs-ru@tensorflow.org list](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ru)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ITh3wzORxgpw"
      },
      "source": [
        "## Setup"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HFbM9dcfxh4l"
      },
      "outputs": [],
      "source": [
        "from __future__ import absolute_import, division, print_function, unicode_literals\n",
        "\n",
        "try:\n",
        "  # %tensorflow_version существует только Colab.\n",
        "  %tensorflow_version 2.x\n",
        "except Exception:\n",
        "  pass\n",
        "import tensorflow as tf\n",
        "\n",
        "tf.keras.backend.clear_session()  # Для простого сброса состояния ноутбука."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZI47-lpfkZ5c"
      },
      "source": [
        "## Введение\n",
        "\n",
        "Вы уже знакомы с использованием `keras.Sequential()` для создания моделей.\n",
        "Functional API позволяет создавать модели более гибко чем  `Sequential`:\n",
        "он может обрабатывать модели с нелинейной топологией, модели с общими слоями,\n",
        "и модели с несколькими входами или выходами.\n",
        "\n",
        "Функциональный подход основан на том, что модель глубокого обучения\n",
        "обычно представляет собой ориентированный ациклический граф (DAG) слоев.\n",
        "Functional API - это набор инструментов для **построения графа слоев**.\n",
        "\n",
        "Рассмотрим следующую модель:\n",
        "\n",
        "```\n",
        "(вход: 784-мерный вектор)\n",
        "       ↧\n",
        "[Плотный слой (64 элемента, активация relu)]\n",
        "       ↧\n",
        "[Плотный слой (64 элемента, активация relu)]\n",
        "       ↧\n",
        "[Плотный слой (10 элементов, активация softmax)]\n",
        "       ↧\n",
        "(выход: вероятностное распределение на 10 классов)\n",
        "```\n",
        "\n",
        "Это простой граф из 3 слоев.\n",
        "\n",
        "Для построения этой модели с помощью Functional API,\n",
        "вам надо начать с создания входного узла:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Yxi0LaSHkDT-"
      },
      "outputs": [],
      "source": [
        "from tensorflow import keras\n",
        "\n",
        "inputs = keras.Input(shape=(784,))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Mr3Z_Pxcnf-H"
      },
      "source": [
        "Здесь мы просто указываем размерность наших данных: 784-мерных векторов.\n",
        "Обратите внимание, что количество данных всегда опускается, мы указываем только размерность каждого элемента.\n",
        "Для ввода предназначенного для изображений размеров `(32, 32, 3)`, мы бы использовали:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0-2Q2nJNneIO"
      },
      "outputs": [],
      "source": [
        "img_inputs = keras.Input(shape=(32, 32, 3))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HoMFNu-pnkgF"
      },
      "source": [
        "То, что возвращает `inputs`, содержит информацию о размерах и типе данных\n",
        "которые вы планируете передать в вашу модель:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ddIr9LPJnibj"
      },
      "outputs": [],
      "source": [
        "inputs.shape"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "lZkLJeQonmTe"
      },
      "outputs": [],
      "source": [
        "inputs.dtype"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kZnhhndTnrzC"
      },
      "source": [
        "Вы создаете новый узел в графе слоев, вызывая слой на этом объекте `inputs`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "sMyyMTqDnpYV"
      },
      "outputs": [],
      "source": [
        "from tensorflow.keras import layers\n",
        "\n",
        "dense = layers.Dense(64, activation='relu')\n",
        "x = dense(inputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "besm-lgFnveV"
      },
      "source": [
        "\"Вызов слоя\" аналогичен рисованию стрелки из \"входных данных\" в созданный нами слой.\n",
        "Мы \"передаем\" входные данные в `dense` слой, и мы получаем `x`.\n",
        "\n",
        "Давайте добавим еще несколько слоев в наш граф слоев:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DbF-MIO2ntf7"
      },
      "outputs": [],
      "source": [
        "x = layers.Dense(64, activation='relu')(x)\n",
        "outputs = layers.Dense(10, activation='softmax')(x)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "B38UlEIlnz_8"
      },
      "source": [
        "Сейчас мы можем создать `Model` указав его входы и выходы в графе слоев:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "MrSfwvl-nx9s"
      },
      "outputs": [],
      "source": [
        "model = keras.Model(inputs=inputs, outputs=outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5EeeV1xJn3jW"
      },
      "source": [
        "Напомним полный процесс определения модели:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "xkz7oqj2n1-q"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(784,), name='img')\n",
        "x = layers.Dense(64, activation='relu')(inputs)\n",
        "x = layers.Dense(64, activation='relu')(x)\n",
        "outputs = layers.Dense(10, activation='softmax')(x)\n",
        "\n",
        "model = keras.Model(inputs=inputs, outputs=outputs, name='mnist_model')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jJzocCbdn6qj"
      },
      "source": [
        "Давайте посмотрим как выглядит сводка модели:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "GirC9odQn5Ep"
      },
      "outputs": [],
      "source": [
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mbNqYAlOn-vA"
      },
      "source": [
        "Мы также можем начертить модель в виде графа:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JYh2wLain8Oi"
      },
      "outputs": [],
      "source": [
        "keras.utils.plot_model(model, 'my_first_model.png')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QtgX2RoGoDZo"
      },
      "source": [
        "И опционально выведем размерности входа и выхода каждого слоя на построенном графе:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7FGesSSuoAG5"
      },
      "outputs": [],
      "source": [
        "keras.utils.plot_model(model, 'my_first_model_with_shape_info.png', show_shapes=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PBZ9XE6LoWvi"
      },
      "source": [
        "Это изображение и код который мы написали идентичны. В версии кода,\n",
        "связывающие стрелки просто заменены операциями вызова.\n",
        "\n",
        "\"Граф слоев\" это очень интуитивный ментальный образ для модели глубокого обучения,\n",
        "а functional API это способ создания моделей которые близко отражают этот ментальный образ."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WUUHMaKLoZDn"
      },
      "source": [
        "## Обучение, оценка и вывод\n",
        "\n",
        "Обучение, оценка и вывод работают для моделей построенных\n",
        "с использованием Functional API точно так же как и в Sequential моделях.\n",
        "\n",
        "Вот быстрая демонстрация.\n",
        "\n",
        "Тут мы загружаем датасет изображений MNIST, преобразуем его в векторы,\n",
        "обучаем модель на данных (мониторя при этом качество работы на проверочной выборке),\n",
        "и наконец мы оцениваем нашу модель на тестовых данных:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DnHvkD22oFEY"
      },
      "outputs": [],
      "source": [
        "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n",
        "x_train = x_train.reshape(60000, 784).astype('float32') / 255\n",
        "x_test = x_test.reshape(10000, 784).astype('float32') / 255\n",
        "\n",
        "model.compile(loss='sparse_categorical_crossentropy',\n",
        "              optimizer=keras.optimizers.RMSprop(),\n",
        "              metrics=['accuracy'])\n",
        "history = model.fit(x_train, y_train,\n",
        "                    batch_size=64,\n",
        "                    epochs=5,\n",
        "                    validation_split=0.2)\n",
        "test_scores = model.evaluate(x_test, y_test, verbose=2)\n",
        "print('Test loss:', test_scores[0])\n",
        "print('Test accuracy:', test_scores[1])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c3nq2fjiLCkE"
      },
      "source": [
        "Полное руководство посвященное обучению и оценки моделей, см. по ссылке [руководство обучения и оценки](./train_and_evaluate.ipynb)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XOsL56zDorLh"
      },
      "source": [
        "## Сохранение и сериализация\n",
        "\n",
        "Сохранение и сериализация для моделей построенных с использованием\n",
        "Functional API работает точно так же как и для Sequential моделей.\n",
        "\n",
        "Стандартным способом сохранения Functional модели является вызов `model.save()` позволяющий сохранить всю модель в один файл.\n",
        "Позже вы можете восстановить ту же модель из этого файла, даже если у вас больше нет доступа к коду\n",
        "создавшему модель.\n",
        "\n",
        "Этот файл включает:\n",
        "- Архитектуру модели\n",
        "- Значения весов модели (которые были получены во время обучения)\n",
        "- Конфигурация обучения модели (то что вы передавали в `compile`)\n",
        "- Оптимизатор и его состояние, если оно было (это позволяет возобновить обучение с того места, где вы остановились)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kN-AO7xvobtr"
      },
      "outputs": [],
      "source": [
        "model.save('path_to_my_model.h5')\n",
        "del model\n",
        "# Восстановить в точности ту же модель исключительно из файла:\n",
        "model = keras.models.load_model('path_to_my_model.h5')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "u0J0tFPHK4pb"
      },
      "source": [
        "Полное руководство по сохранению моделей см. в [Руководство по сохранению и сериализации моделей](./save_and_serialize.ipynb)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lKz1WWr2LUzF"
      },
      "source": [
        "## Использование одного и того же графа слоев для определения нескольких моделей\n",
        "\n",
        "\n",
        "В functional API, модели создаются путем указания входных\n",
        "и выходных данных в графе слоев. Это значит что один граф слоев\n",
        "может быть использован для генерации нескольких моделей.\n",
        "\n",
        "В приведенном ниже примере мы используем один и тот же стек слоев для создания двух моделей:\n",
        "модель `кодировщика (encoder)` которая преобразует входные изображения в 16-мерные вектора,\n",
        "и сквозную модель `автокодировщика (autoencoder)` для обучения.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WItZQr6LuVbF"
      },
      "outputs": [],
      "source": [
        "encoder_input = keras.Input(shape=(28, 28, 1), name='img')\n",
        "x = layers.Conv2D(16, 3, activation='relu')(encoder_input)\n",
        "x = layers.Conv2D(32, 3, activation='relu')(x)\n",
        "x = layers.MaxPooling2D(3)(x)\n",
        "x = layers.Conv2D(32, 3, activation='relu')(x)\n",
        "x = layers.Conv2D(16, 3, activation='relu')(x)\n",
        "encoder_output = layers.GlobalMaxPooling2D()(x)\n",
        "\n",
        "encoder = keras.Model(encoder_input, encoder_output, name='encoder')\n",
        "encoder.summary()\n",
        "\n",
        "x = layers.Reshape((4, 4, 1))(encoder_output)\n",
        "x = layers.Conv2DTranspose(16, 3, activation='relu')(x)\n",
        "x = layers.Conv2DTranspose(32, 3, activation='relu')(x)\n",
        "x = layers.UpSampling2D(3)(x)\n",
        "x = layers.Conv2DTranspose(16, 3, activation='relu')(x)\n",
        "decoder_output = layers.Conv2DTranspose(1, 3, activation='relu')(x)\n",
        "\n",
        "autoencoder = keras.Model(encoder_input, decoder_output, name='autoencoder')\n",
        "autoencoder.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oNeg3WWFuYZK"
      },
      "source": [
        "Обратите внимание, что мы делаем архитектуру декодирования строго симметричной архитектуре кодирования,\n",
        "таким образом мы получим размерность выходных данных такую же как и входных данных `(28, 28, 1)`.\n",
        "Обратным к слою `Conv2D` является слой `Conv2DTranspose`, а обратным к слою `MaxPooling2D`\n",
        "будет слой `UpSampling2D`."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "h1FVW4j-uc6Y"
      },
      "source": [
        "## Модели можно вызывать как слои\n",
        "\n",
        "Вы можете использовать любую модель так, как если бы это был слой передавая ее в `Input` или на выход другого слоя.\n",
        "Обратите внимание, что вызывая модель, вы не только переиспользуете ее архитектуру, вы также повторно используете ее веса.\n",
        "\n",
        "Давайте посмотрим на это в действии. Вот другой взгляд на пример автокодировщика, когда создается модель кодировщика, модель декодировщика, и они связываются в два вызова для получения модели автокодировщика:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Ld7KdsQ_uZbr"
      },
      "outputs": [],
      "source": [
        "encoder_input = keras.Input(shape=(28, 28, 1), name='original_img')\n",
        "x = layers.Conv2D(16, 3, activation='relu')(encoder_input)\n",
        "x = layers.Conv2D(32, 3, activation='relu')(x)\n",
        "x = layers.MaxPooling2D(3)(x)\n",
        "x = layers.Conv2D(32, 3, activation='relu')(x)\n",
        "x = layers.Conv2D(16, 3, activation='relu')(x)\n",
        "encoder_output = layers.GlobalMaxPooling2D()(x)\n",
        "\n",
        "encoder = keras.Model(encoder_input, encoder_output, name='encoder')\n",
        "encoder.summary()\n",
        "\n",
        "decoder_input = keras.Input(shape=(16,), name='encoded_img')\n",
        "x = layers.Reshape((4, 4, 1))(decoder_input)\n",
        "x = layers.Conv2DTranspose(16, 3, activation='relu')(x)\n",
        "x = layers.Conv2DTranspose(32, 3, activation='relu')(x)\n",
        "x = layers.UpSampling2D(3)(x)\n",
        "x = layers.Conv2DTranspose(16, 3, activation='relu')(x)\n",
        "decoder_output = layers.Conv2DTranspose(1, 3, activation='relu')(x)\n",
        "\n",
        "decoder = keras.Model(decoder_input, decoder_output, name='decoder')\n",
        "decoder.summary()\n",
        "\n",
        "autoencoder_input = keras.Input(shape=(28, 28, 1), name='img')\n",
        "encoded_img = encoder(autoencoder_input)\n",
        "decoded_img = decoder(encoded_img)\n",
        "autoencoder = keras.Model(autoencoder_input, decoded_img, name='autoencoder')\n",
        "autoencoder.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "icQFny_huiXC"
      },
      "source": [
        "Как вы видите, модель может быть вложена: модель может содержать подмодель (поскольку модель можно рассматривать как слой).\n",
        "\n",
        "Распространенным вариантом использования вложения моделей является *ensembling*.\n",
        "Вот пример того, как можно объединить набор моделей в одну модель которая усредняет их прогнозы:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ZBlZbRn5uk-9"
      },
      "outputs": [],
      "source": [
        "def get_model():\n",
        "  inputs = keras.Input(shape=(128,))\n",
        "  outputs = layers.Dense(1, activation='sigmoid')(inputs)\n",
        "  return keras.Model(inputs, outputs)\n",
        "\n",
        "model1 = get_model()\n",
        "model2 = get_model()\n",
        "model3 = get_model()\n",
        "\n",
        "inputs = keras.Input(shape=(128,))\n",
        "y1 = model1(inputs)\n",
        "y2 = model2(inputs)\n",
        "y3 = model3(inputs)\n",
        "outputs = layers.average([y1, y2, y3])\n",
        "ensemble_model = keras.Model(inputs=inputs, outputs=outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e1za1TZxuoId"
      },
      "source": [
        "## Манипулирование сложными топологиями графов\n",
        "\n",
        "\n",
        "### Модели с несколькими входами и выходами\n",
        "\n",
        "Functional API упрощает манипуляции с несколькими входами и выходами.\n",
        "Это не может быть сделано с использованием Sequential API.\n",
        "\n",
        "Вот простой пример.\n",
        "\n",
        "Допустим, вы создаете систему для ранжирования клиентских заявок по приоритетам и направления их в нужный отдел.\n",
        "\n",
        "У вашей модели будет 3 входа:\n",
        "\n",
        "- Заголовок заявки (текстовые входные данные)\n",
        "- Текстовое содержание заявки (текстовые входные данные)\n",
        "- Любые теги добавленные пользователем (категорийные входные данные)\n",
        "\n",
        "У модели будет 2 выхода:\n",
        "\n",
        "- Оценка приоритета между 0 и 1 (скаляр, результат сигмоидного выхода)\n",
        "- Отдел который должен обработать заявку (softmax выход относительно множества отделов)\n",
        "\n",
        "Давайте построим модель в несколько строк с помощью Functional API."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Gt91OtzbutJy"
      },
      "outputs": [],
      "source": [
        "num_tags = 12  # Количество различных тегов проблем\n",
        "num_words = 10000  # Размер словаря полученный в результате предобработки текстовых данных\n",
        "num_departments = 4  # Количество отделов для предсказаний\n",
        "\n",
        "title_input = keras.Input(shape=(None,), name='title')  # Последовательность целых чисел переменной длины\n",
        "body_input = keras.Input(shape=(None,), name='body')  # Последовательность целых чисел переменной длины\n",
        "tags_input = keras.Input(shape=(num_tags,), name='tags')  # Бинарный вектор размера `num_tags`\n",
        "\n",
        "# Вложим каждое слово заголовка в 64-мерный вектор\n",
        "title_features = layers.Embedding(num_words, 64)(title_input)\n",
        "# Вложим каждое слово текста в 64-мерный вектор\n",
        "body_features = layers.Embedding(num_words, 64)(body_input)\n",
        "\n",
        "# Сокращаем последовательность вложенных слов заголовка до одного 128-мерного вектора\n",
        "title_features = layers.LSTM(128)(title_features)\n",
        "# Сокращаем последовательность вложенных слов содержимого до одного 32-мерного вектора\n",
        "body_features = layers.LSTM(32)(body_features)\n",
        "\n",
        "# Объединим все признаки в один вектор с помощью конкатенации\n",
        "x = layers.concatenate([title_features, body_features, tags_input])\n",
        "\n",
        "# Добавим логистическую регрессию для прогнозирования приоритета по признакам\n",
        "priority_pred = layers.Dense(1, activation='sigmoid', name='priority')(x)\n",
        "# Добавим классификатор отделов прогнозирующий на признаках\n",
        "department_pred = layers.Dense(num_departments, activation='softmax', name='department')(x)\n",
        "\n",
        "# Создание сквозной модели, прогнозирующей приоритет и отдел\n",
        "model = keras.Model(inputs=[title_input, body_input, tags_input],\n",
        "                    outputs=[priority_pred, department_pred])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "KIS7lqW0uwh-"
      },
      "source": [
        "Давайте начертим граф модели:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IMij4gzhuzYV"
      },
      "outputs": [],
      "source": [
        "keras.utils.plot_model(model, 'multi_input_and_output_model.png', show_shapes=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oOyuig2Hu00p"
      },
      "source": [
        "При компиляции этой модели, мы можем присвоить различные функции потерь каждому выходу.\n",
        "Вы даже можете присвоить разные веса каждой функции потерь, чтобы варьировать их\n",
        "вклад в общую функцию потерь обучения."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Crtdpi5Uu2cX"
      },
      "outputs": [],
      "source": [
        "model.compile(optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "              loss=['binary_crossentropy', 'categorical_crossentropy'],\n",
        "              loss_weights=[1., 0.2])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "t42Jrn0Yu5jL"
      },
      "source": [
        "Так как мы дали имена нашим выходным слоям, мы можем также указать именованные функции потерь:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dPM0EwW_u6mV"
      },
      "outputs": [],
      "source": [
        "model.compile(optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "              loss={'priority': 'binary_crossentropy',\n",
        "                    'department': 'categorical_crossentropy'},\n",
        "              loss_weights=[1., 0.2])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bpTx2sXnu3-W"
      },
      "source": [
        "Мы можем обучить модель передавая входные данные(списки массивов Numpy) и метки:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "nB-upOoGu_k4"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "\n",
        "# Учебные входные данные\n",
        "title_data = np.random.randint(num_words, size=(1280, 10))\n",
        "body_data = np.random.randint(num_words, size=(1280, 100))\n",
        "tags_data = np.random.randint(2, size=(1280, num_tags)).astype('float32')\n",
        "# Учебные целевые данные\n",
        "priority_targets = np.random.random(size=(1280, 1))\n",
        "dept_targets = np.random.randint(2, size=(1280, num_departments))\n",
        "\n",
        "history = model.fit({'title': title_data, 'body': body_data, 'tags': tags_data},\n",
        "          {'priority': priority_targets, 'department': dept_targets},\n",
        "          epochs=2,\n",
        "          batch_size=32)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qNguhBWuvCtz"
      },
      "source": [
        "При вызове fit с объектом `Dataset`, должны возвращаться либо\n",
        "кортеж списков, таких как `([title_data, body_data, tags_data], [priority_targets, dept_targets])`\n",
        "либо кортеж словарей\n",
        "`({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets})`.\n",
        "\n",
        "Для более подробного объяснения обратитесь к полному руководству [руководство по обучению и оценке](./train_and_evaluate.ipynb)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tR0X5tTOvPyg"
      },
      "source": [
        "### Учебная ResNet модель\n",
        "\n",
        "В дополнение к моделям с несколькими входами и выходами,\n",
        "Functional API упрощает манипулирование топологиями с нелинейной связностью,\n",
        "то есть моделями, в которых слои не связаны последовательно.\n",
        "Это также не может быть реализовано с помощью Sequential API (это видно из названия).\n",
        "\n",
        "Распространенный пример использования этого - residual connections.\n",
        "\n",
        "Давайте построим учебную ResNet модель для CIFAR10 чтобы продемонстрировать это."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "VzMoYrMNvXrm"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(32, 32, 3), name='img')\n",
        "x = layers.Conv2D(32, 3, activation='relu')(inputs)\n",
        "x = layers.Conv2D(64, 3, activation='relu')(x)\n",
        "block_1_output = layers.MaxPooling2D(3)(x)\n",
        "\n",
        "x = layers.Conv2D(64, 3, activation='relu', padding='same')(block_1_output)\n",
        "x = layers.Conv2D(64, 3, activation='relu', padding='same')(x)\n",
        "block_2_output = layers.add([x, block_1_output])\n",
        "\n",
        "x = layers.Conv2D(64, 3, activation='relu', padding='same')(block_2_output)\n",
        "x = layers.Conv2D(64, 3, activation='relu', padding='same')(x)\n",
        "block_3_output = layers.add([x, block_2_output])\n",
        "\n",
        "x = layers.Conv2D(64, 3, activation='relu')(block_3_output)\n",
        "x = layers.GlobalAveragePooling2D()(x)\n",
        "x = layers.Dense(256, activation='relu')(x)\n",
        "x = layers.Dropout(0.5)(x)\n",
        "outputs = layers.Dense(10, activation='softmax')(x)\n",
        "\n",
        "model = keras.Model(inputs, outputs, name='toy_resnet')\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ISQX32bgrkis"
      },
      "source": [
        "Давайте начертим модель:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pNFVkAd3rlCM"
      },
      "outputs": [],
      "source": [
        "keras.utils.plot_model(model, 'mini_resnet.png', show_shapes=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ECcG87yZrxp5"
      },
      "source": [
        "Давайте обучим ее:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_iXGz5XEryou"
      },
      "outputs": [],
      "source": [
        "(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()\n",
        "x_train = x_train.astype('float32') / 255.\n",
        "x_test = x_test.astype('float32') / 255.\n",
        "y_train = keras.utils.to_categorical(y_train, 10)\n",
        "y_test = keras.utils.to_categorical(y_test, 10)\n",
        "\n",
        "model.compile(optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "              loss='categorical_crossentropy',\n",
        "              metrics=['acc'])\n",
        "model.fit(x_train, y_train,\n",
        "          batch_size=64,\n",
        "          epochs=1,\n",
        "          validation_split=0.2)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XQfg0JUkr7SH"
      },
      "source": [
        "## Совместное использование слоев\n",
        "\n",
        "Другим хорошим использованием functional API являются модели, использующие общие слои. Общие слои - это экземпляры слоев, которые переиспользуются в одной и той же модели: они изучают признаки, которые относятся к нескольким путям в графе слоев.\n",
        "\n",
        "Общие слои часто используются для кодирования входных данных, которые поступают из одинаковых пространств (скажем, из двух разных фрагментов текста, имеющих одинаковый словарь), поскольку они обеспечивают обмен информацией между этими различными данными, что позволяет обучать такие модели на меньшем количестве данных. Если определенное слово появилось на одном из входов, это будет способствовать его обработке на всех входах, которые проходят через общий уровень.\n",
        "\n",
        "Чтобы совместно использовать слой в Functional API, просто вызовите тот же экземпляр слоя несколько раз. Например, здесь слой `Embedding` используется совместно на двух текстовых входах:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "R9pAPQCnKuMR"
      },
      "outputs": [],
      "source": [
        "# Вложения для 1000 различных слов в 128-мерные вектора\n",
        "shared_embedding = layers.Embedding(1000, 128)\n",
        "\n",
        "# Целочисленные последовательности переменной длины\n",
        "text_input_a = keras.Input(shape=(None,), dtype='int32')\n",
        "\n",
        "# Целочисленные последовательности переменной длины\n",
        "text_input_b = keras.Input(shape=(None,), dtype='int32')\n",
        "\n",
        "# Мы переиспользуем тот же слой для кодирования на обоих входах\n",
        "encoded_input_a = shared_embedding(text_input_a)\n",
        "encoded_input_b = shared_embedding(text_input_b)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xNEKvfUpr-Kf"
      },
      "source": [
        "## Извлечение и повторное использование узлов в графе слоев"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JHVGI6bEr-ze"
      },
      "source": [
        "Поскольку граф слоев, которыми вы манипулируете в Functional API, является статической структурой данных, к ней можно получить доступ и проверить ее. Именно так мы строим Functional модели, например, в виде изображений.\n",
        "\n",
        "Это также означает, что мы можем получить доступ к активациям промежуточных слоев (\"узлов\" в графе) и использовать их в других местах. Это чрезвычайно полезно для извлечения признаков, например!\n",
        "\n",
        "Давайте посмотрим пример. Это модель VGG19 с весами предобученными на ImageNet:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "c-gl3xHBH-oX"
      },
      "outputs": [],
      "source": [
        "from tensorflow.keras.applications import VGG19\n",
        "\n",
        "vgg19 = VGG19()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AKefin_xIGBP"
      },
      "source": [
        "И это промежуточные активации модели, полученные путем запроса к структуре данных графа:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1_Ap05fgIRgE"
      },
      "outputs": [],
      "source": [
        "features_list = [layer.output for layer in vgg19.layers]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "H1zx5qM7IYu4"
      },
      "source": [
        "Мы можем использовать эти признаки для создания новой модели извлечения признаков, которая возвращает значения активаций промежуточного уровня - и мы можем сделать все это в 3 строчки"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NrU82Pa8Igwo"
      },
      "outputs": [],
      "source": [
        "feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)\n",
        "\n",
        "img = np.random.random((1, 224, 224, 3)).astype('float32')\n",
        "extracted_features = feat_extraction_model(img)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "G-e2-jNCLIqy"
      },
      "source": [
        "Это удобно когда [реализуется neural style transfer](https://medium.com/tensorflow/neural-style-transfer-creating-art-with-deep-learning-using-tf-keras-and-eager-execution-7d541ac31398), как и в других случаях."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "t9M2Uvi3sBy0"
      },
      "source": [
        "## Расширение API при помощи написания кастомных слоев\n",
        "\n",
        "tf.keras обладает широким набором встроенных слоев. Вот несколько примеров:\n",
        "\n",
        "- Сверточные слои: `Conv1D`, `Conv2D`, `Conv3D`, `Conv2DTranspose`, и т.д.\n",
        "- Слои пулинга: `MaxPooling1D`, `MaxPooling2D`, `MaxPooling3D`, `AveragePooling1D`, и т.д.\n",
        "- Слои RNN: `GRU`, `LSTM`, `ConvLSTM2D`, и т.д.\n",
        "- `BatchNormalization`, `Dropout`, `Embedding`, и т.д.\n",
        "\n",
        "Если вы не нашли то, что вам нужно, легко расширить API создав собственный слой.\n",
        "\n",
        "Все слои наследуют класс `Layer` и реализуют:\n",
        "- Метод `call`, определяющий вычисления выполняемые слоем.\n",
        "- Метод `build`, создающий веса слоя (заметим что это всего лишь стилевое соглашение; вы можете также создать веса в `__init__`).\n",
        "\n",
        "Чтобы узать больше о создании слоев с нуля, проверьте руководство [Руководство по написанию слоев и моделей с нуля](./custom_layers_and_models.ipynb).\n",
        "\n",
        "Вот простая реализация `Dense` слоя:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ztAmarbgNV6V"
      },
      "outputs": [],
      "source": [
        "class CustomDense(layers.Layer):\n",
        "\n",
        "  def __init__(self, units=32):\n",
        "    super(CustomDense, self).__init__()\n",
        "    self.units = units\n",
        "\n",
        "  def build(self, input_shape):\n",
        "    self.w = self.add_weight(shape=(input_shape[-1], self.units),\n",
        "                             initializer='random_normal',\n",
        "                             trainable=True)\n",
        "    self.b = self.add_weight(shape=(self.units,),\n",
        "                             initializer='random_normal',\n",
        "                             trainable=True)\n",
        "\n",
        "  def call(self, inputs):\n",
        "    return tf.matmul(inputs, self.w) + self.b\n",
        "\n",
        "inputs = keras.Input((4,))\n",
        "outputs = CustomDense(10)(inputs)\n",
        "\n",
        "model = keras.Model(inputs, outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NXxp_32bNWTy"
      },
      "source": [
        "Если вы хотите, чтобы ваш пользовательский слой поддерживал сериализацию, вы также должны определить метод` get_config`,возвращающий аргументы конструктора экземпляра слоя:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "K3OQ4XxzNfAZ"
      },
      "outputs": [],
      "source": [
        "class CustomDense(layers.Layer):\n",
        "\n",
        "  def __init__(self, units=32):\n",
        "    super(CustomDense, self).__init__()\n",
        "    self.units = units\n",
        "\n",
        "  def build(self, input_shape):\n",
        "    self.w = self.add_weight(shape=(input_shape[-1], self.units),\n",
        "                             initializer='random_normal',\n",
        "                             trainable=True)\n",
        "    self.b = self.add_weight(shape=(self.units,),\n",
        "                             initializer='random_normal',\n",
        "                             trainable=True)\n",
        "\n",
        "  def call(self, inputs):\n",
        "    return tf.matmul(inputs, self.w) + self.b\n",
        "\n",
        "  def get_config(self):\n",
        "    return {'units': self.units}\n",
        "\n",
        "\n",
        "inputs = keras.Input((4,))\n",
        "outputs = CustomDense(10)(inputs)\n",
        "\n",
        "model = keras.Model(inputs, outputs)\n",
        "config = model.get_config()\n",
        "\n",
        "new_model = keras.Model.from_config(\n",
        "    config, custom_objects={'CustomDense': CustomDense})"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kXg6hZN_NfN8"
      },
      "source": [
        "Опционально, вы также можете реализовать метод класса `from_config (cls, config)`, который отвечает за пересоздание экземпляра слоя, учитывая его словарь конфигурации. Реализация по умолчанию` from_config` выглядит так:\n",
        "\n",
        "```python\n",
        "def from_config(cls, config):\n",
        "  return cls(**config)\n",
        "```"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ifOVqn84sCNU"
      },
      "source": [
        "## Когда использовать Functional API\n",
        "\n",
        "Как определить когда лучше использовать Functional API для создания новой модели, или просто наследовать класс `Model` напрямую?\n",
        "\n",
        "В целом, Functional API более высокоуровневый и простой в использовании, он имеет ряд функций, которые не поддерживаются наследуемыми `Model`.\n",
        "\n",
        "Однако, наследование `Model` дает вам большую гибкость при создании моделей, которые не описываются легко в виде направленного ациклического графа слоев (например, вы не сможете реализовать Tree-RNN с Functional API, вам нужно напрямую наследоваться от `Model`).\n",
        "\n",
        "\n",
        "### Cильные стороны Functional API:\n",
        "\n",
        "Свойства перечисленные ниже являются верными и для Sequential моделей (которые также являются структурами данных), но не верны для наследуемых моделей (которые представляют собой код Python, а не структуры данных).\n",
        "\n",
        "\n",
        "#### С Functional API получается более короткий код.\n",
        "\n",
        "Нет `super(MyClass, self).__init__(...)`, нет `def call(self, ...):`, и т.д.\n",
        "\n",
        "Сравните:\n",
        "\n",
        "```python\n",
        "inputs = keras.Input(shape=(32,))\n",
        "x = layers.Dense(64, activation='relu')(inputs)\n",
        "outputs = layers.Dense(10)(x)\n",
        "mlp = keras.Model(inputs, outputs)\n",
        "```\n",
        "\n",
        "С наследуемой версией:\n",
        "\n",
        "```python\n",
        "class MLP(keras.Model):\n",
        "\n",
        "  def __init__(self, **kwargs):\n",
        "    super(MLP, self).__init__(**kwargs)\n",
        "    self.dense_1 = layers.Dense(64, activation='relu')\n",
        "    self.dense_2 = layers.Dense(10)\n",
        "\n",
        "  def call(self, inputs):\n",
        "    x = self.dense_1(inputs)\n",
        "    return self.dense_2(x)\n",
        "\n",
        "# Создадим экземпляр модели.\n",
        "mlp = MLP()\n",
        "# Необходимо создать состояние модели.\n",
        "# У модели нет состояния пока она не была вызвана хотя бы раз.\n",
        "_ = mlp(tf.zeros((1, 32)))\n",
        "```\n",
        "\n",
        "\n",
        "#### Он валидирует вашу модель пока вы ее определяете.\n",
        "\n",
        "В Functional API входные спецификации (shape и dtype) создаются заранее (через `Input`), и каждый раз, когда вы вызываете слой, слой проверяет, что спецификации переданные ему соответствует его предположениям, если это не так то вы получите полезное сообщение об ошибке.\n",
        "\n",
        "Это гарантирует, что любая модель которую вы построите с Functional API запустится. Вся отладка (не относящаяся к отладке сходимости) будет происходить статично во время конструирования модели, а не во время выполнения. Это аналогично проверке типа в компиляторе.\n",
        "\n",
        "\n",
        "#### Вашу Functional модель можно представить графически, а также она проверяема.\n",
        "\n",
        "Вы можете начертить модель в виде графа, и вы легко можете получить доступ к промежуточным узлам графа, например, чтобы извлечь и переиспользовать активации промежуточных слоев, как мы видели в предыдущем примере:\n",
        "\n",
        "```python\n",
        "features_list = [layer.output for layer in vgg19.layers]\n",
        "feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)\n",
        "```\n",
        "\n",
        "\n",
        "#### Ваша Functional модель может быть сериализована или клонирована.\n",
        "\n",
        "Поскольку Functional модель это скорее структура данных чем кусок кода, она безопасно сериализуется и может быть сохранена в виде одного файла который позволяет вам воссоздать в точности ту же модель без доступа к исходному коду. Смотрите наше [руководство по сохранению и сериализации](./save_and_serialize.ipynb) для лучшего понимания.\n",
        "\n",
        "\n",
        "### Слабые стороны Functional API:\n",
        "\n",
        "\n",
        "#### Он не поддерживает динамичные архитектуры.\n",
        "\n",
        "Functional API обрабатывает модели как группы DAG слоев. Это справедливо для большинства архитектур глубокого обучения, но не для всех: например, рекурсивные сети или Tree RNN не соответствуют этому предположению и не могут быть реализованы в Functional API.\n",
        "\n",
        "\n",
        "#### Иногда вам просто нужно написать все с нуля.\n",
        "\n",
        "При написании продвинутых архитектур вы можете захотеть сделать то, что выходит за рамки \"определения DAG слоев\": например, вы можете использовать несколько пользовательских методов обучения и вывода на экземпляре вашей модели. Это требует наследования `Model`.\n",
        "\n",
        "\n",
        "---\n",
        "\n",
        "\n",
        "Чтобы лучше понять разницу между Functional API и наследованием `Model`, советуем прочитать [Что такое Symbolic и Imperative API в TensorFlow 2.0?](https://medium.com/tensorflow/what-are-symbolic-and-imperative-apis-in-tensorflow-2-0-dfccecb01021)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ym1jrCqusGvj"
      },
      "source": [
        "## Сочетание различных стилей API\n",
        "\n",
        "Важно отметить, что выбор между Functional API или наследованием `Model` не является бинарным решением, которое ограничивает вас одной категорией моделей. Все модели в API tf.keras могут взаимодействовать друг с другом, будь то Sequential модели, Functional модели или наследуемые Models/Layers, написанные с нуля.\n",
        "\n",
        "Вы всегда можете использовать Functional модель или Sequential модель как часть наследуемой Model/Layer:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9zF5YTLy_vGZ"
      },
      "outputs": [],
      "source": [
        "units = 32\n",
        "timesteps = 10\n",
        "input_dim = 5\n",
        "\n",
        "# Define a Functional model\n",
        "inputs = keras.Input((None, units))\n",
        "x = layers.GlobalAveragePooling1D()(inputs)\n",
        "outputs = layers.Dense(1, activation='sigmoid')(x)\n",
        "model = keras.Model(inputs, outputs)\n",
        "\n",
        "\n",
        "class CustomRNN(layers.Layer):\n",
        "\n",
        "  def __init__(self):\n",
        "    super(CustomRNN, self).__init__()\n",
        "    self.units = units\n",
        "    self.projection_1 = layers.Dense(units=units, activation='tanh')\n",
        "    self.projection_2 = layers.Dense(units=units, activation='tanh')\n",
        "    # Our previously-defined Functional model\n",
        "    self.classifier = model\n",
        "\n",
        "  def call(self, inputs):\n",
        "    outputs = []\n",
        "    state = tf.zeros(shape=(inputs.shape[0], self.units))\n",
        "    for t in range(inputs.shape[1]):\n",
        "      x = inputs[:, t, :]\n",
        "      h = self.projection_1(x)\n",
        "      y = h + self.projection_2(state)\n",
        "      state = y\n",
        "      outputs.append(y)\n",
        "    features = tf.stack(outputs, axis=1)\n",
        "    print(features.shape)\n",
        "    return self.classifier(features)\n",
        "\n",
        "rnn_model = CustomRNN()\n",
        "_ = rnn_model(tf.zeros((1, timesteps, input_dim)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oxW1d0a8_ufg"
      },
      "source": [
        "Вы можете использовать любой Layer или Model, наследующий keras.Model в Functional API в том случае если реализован метод `call` который соответствует одному из следующих паттернов:\n",
        "\n",
        "- `call(self, inputs, **kwargs)`  где `inputs` это тензор или вложенная струтура тензоров (напр. список тензоров), и где `**kwargs` это нетензорные аргументы (не входные данные).\n",
        "- `call(self, inputs, training=None, **kwargs)` где `training` это булево значение показывающее в каком режиме должен вести себя слой - обучения или вывода.\n",
        "- `call(self, inputs, mask=None, **kwargs)` где `mask` это тензор булевой маски (полезно для RNN, например).\n",
        "- `call(self, inputs, training=None, mask=None, **kwargs)` -- конечно вы можете иметь одновременно все параметры, определяющие поведение слоя.\n",
        "\n",
        "В дополнение, если вы реализуете метод `get_config` на вашем пользовательском Layer или Model, Functional модели которые вы создадите с ним, будут сериализуемы и клонируемы.\n",
        "\n",
        "Далее приведем небольшой пример где мы используем кастомный RNN написанный с нуля в стиле Functional API:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TmTEZ6F3ArJR"
      },
      "outputs": [],
      "source": [
        "units = 32\n",
        "timesteps = 10\n",
        "input_dim = 5\n",
        "batch_size = 16\n",
        "\n",
        "\n",
        "class CustomRNN(layers.Layer):\n",
        "\n",
        "  def __init__(self):\n",
        "    super(CustomRNN, self).__init__()\n",
        "    self.units = units\n",
        "    self.projection_1 = layers.Dense(units=units, activation='tanh')\n",
        "    self.projection_2 = layers.Dense(units=units, activation='tanh')\n",
        "    self.classifier = layers.Dense(1, activation='sigmoid')\n",
        "\n",
        "  def call(self, inputs):\n",
        "    outputs = []\n",
        "    state = tf.zeros(shape=(inputs.shape[0], self.units))\n",
        "    for t in range(inputs.shape[1]):\n",
        "      x = inputs[:, t, :]\n",
        "      h = self.projection_1(x)\n",
        "      y = h + self.projection_2(state)\n",
        "      state = y\n",
        "      outputs.append(y)\n",
        "    features = tf.stack(outputs, axis=1)\n",
        "    return self.classifier(features)\n",
        "\n",
        "# Заметьте, что мы задаем статичный размер пакета для входных данных с\n",
        "# аргументом `batch_shape`, потому что внутренние вычисления `CustomRNN` требуют фиксированного размера пакета\n",
        "# (когда мы создает нулевые тензоры `state`).\n",
        "inputs = keras.Input(batch_shape=(batch_size, timesteps, input_dim))\n",
        "x = layers.Conv1D(32, 3)(inputs)\n",
        "outputs = CustomRNN()(x)\n",
        "\n",
        "model = keras.Model(inputs, outputs)\n",
        "\n",
        "rnn_model = CustomRNN()\n",
        "_ = rnn_model(tf.zeros((1, 10, 5)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6VxcYb4qArlb"
      },
      "source": [
        "Это завершает наше руководство по Functional API!\n",
        "\n",
        "Теперь у вас под рукой мощный набор инструментов для построения моделей глубокого обучения."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "functional.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
